using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Communication
{
	public class TcpServer : IServer
	{
		public TcpServer(IRequestHandler handler, IPEndPoint endPoint)
		{
			this.handler = handler;
			listener = new TcpListener(endPoint);
		}

		public TcpServer(IRequestHandler handler, IPAddress ip, int port)
			: this (handler, new IPEndPoint(ip, port)) { }

		public void Start()
		{
			listener.Start(int.MaxValue);
			try
			{
				while (true)
				{
					var client = listener.AcceptTcpClient();
					ThreadPool.QueueUserWorkItem(obj => HandleClient(client));
				}
			}
			catch (Exception error)
			{
				LogListenError(error);
			}
		}

		public void StartAsync()
		{
			new Thread(Start).Start();
		}

		public void Stop()
		{
			listener.Stop();
		}

		public void Dispose()
		{
			Stop();
		}

		private void HandleClient(TcpClient client)
		{

			Stream clientStream = null;
			try
			{
				clientStream = client.GetStream();
				var request = ReadRequest(clientStream);
				handler.Handle(new RequestContext(request, clientStream));
			}
			catch (Exception error)
			{
				LogError(error);
			}
			finally
			{
				if (clientStream != null)
					clientStream.Close();
			}
		}

		private static MemoryStream ReadRequest(Stream clientStream)
		{
			var buffer = new byte[4096];
			var request = new MemoryStream();
			int totalRead = 0;

			clientStream.Read(buffer, 0, sizeof(int));
			int requestLength = BitConverter.ToInt32(buffer, 0);

			while (true)
			{
				if (totalRead >= requestLength)
					break;
				int bytesRead = clientStream.Read(buffer, 0, Math.Min(buffer.Length, requestLength - totalRead));
				if (bytesRead <= 0)
					break;
				request.Write(buffer, 0, bytesRead);
				totalRead += bytesRead;
			}

			return request;
		}

		private static void LogListenError(Exception error)
		{
			Console.Out.WriteLine("TcpServer. Error in accepting client: {0}", error);
		}

		private static void LogError(Exception error)
		{
			Console.Out.WriteLine("TcpServer. Error in processing request: {0}", error);
		}

		private readonly IRequestHandler handler;

		private readonly TcpListener listener;
	}
}